﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Threading;
using ICSharpCode.SharpZipLib.Zip;
using MediaController.Subs.Interfaces;

namespace MediaController.Subs.Provider
{
    public abstract class SubtitlesProvider : ISubsDownloader
    {
        private object _syncObject = new object();

        private int _numOfOpenThreads;

        public event Action<SubsProgressEventArgs> SearchedFileChanged;

        public event Action<SubsProgressEventArgs> ProgressChanged;

        public event Action<SubsProgressEventArgs> FileFounded;

        public event Action<object, NoMatchTitleFoundEventArgs> NoTitleMachFound;

        public event Action<object, NoMatchSubtitleFoundEventArgs> NoSubtitleMachFound;

        /// <summary>
        /// Indecate whater to comapre the movie result by year or not.
        /// if not, the first result that comes will be taken
        /// </summary>
        public bool CompareYear { get; set; }

        public bool SearchByFolderName { get; set; }

        public int ThreadCount { get; set; }

        public bool SmartSearch { get; set; }

        protected SubtitlesProvider()
        {
            SearchedFileChanged += OnProgressChanged;
            NoTitleMachFound += OnNoTitleMachFound;
            NoSubtitleMachFound += OnNoSubtitleMachFound;
        }

        public void DownloadSubtitle(string fileName)
        {
            var titleFileInfo = new TitleFileInfo(fileName);
            DownloadSubtitle(titleFileInfo);
        }

        public void DownloadSubtitle(string path, SearchOption searchOption)
        {
            InvokeProgressChanged(new SubsProgressEventArgs(null, 0, string.Format("Searching subtitles under [{0}]", path)));

            var titlesFolderInfo = new TitlesFolderInfo(path, searchOption);
            foreach (var mediaFileInfo in titlesFolderInfo.MediaFileInfos)
            {
                InvokeFileFounded(new SubsProgressEventArgs(mediaFileInfo, 0, mediaFileInfo));
            }

            for (int i = 0; i < titlesFolderInfo.MediaFileInfos.Length; i++)
            {
                var t = i * 1.0 / titlesFolderInfo.MediaFileInfos.Length * 1.0;
                double precentage = t * 100.0;

                var mediaFile = titlesFolderInfo.MediaFileInfos[i];
                if (mediaFile.HasSubs) continue;

                InvokeSearchedFileChanged(new SubsProgressEventArgs(mediaFile, Convert.ToInt32(precentage), mediaFile.TitleName));

                while (ThreadCount == _numOfOpenThreads)
                {
                    Thread.Sleep(1000);
                }

                ThreadPool.QueueUserWorkItem(stat =>
                                                 {
                                                     Interlocked.Increment(ref _numOfOpenThreads);
                                                     DownloadSubtitle(mediaFile);
                                                     Interlocked.Decrement(ref _numOfOpenThreads);
                                                 });
                Thread.Sleep(500);

            }
            Thread.Sleep(500);
            while (_numOfOpenThreads != 0)
            {
                Thread.Sleep(1000);
            }
        }

        public void FindSubtitles(TitleFileInfo mediaFileInfo, TitleResult machedMovied)
        {
            InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("Maching subtitles results result for {0}", mediaFileInfo.TitleName)));
            SubtitleResult subtitleResult = FindSubtitleResult(mediaFileInfo, machedMovied);
            if (subtitleResult == null)
            {
                InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("No subtitles mach founded for {0}", mediaFileInfo.TitleName)));
                return;
            }

            InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("Downloading subtitles for {0}", mediaFileInfo.TitleName)));
            DownloadSubtitle(subtitleResult.Link, mediaFileInfo);

        }

        protected void DownloadSubtitle(TitleFileInfo mediaFileInfo)
        {
            mediaFileInfo = SearchByFolderName ? mediaFileInfo.TitleFileInfoByFolderName : mediaFileInfo;
            InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("Searching title {0}", mediaFileInfo.TitleName)));
            TitleResult[] movieResults = GetTitles(mediaFileInfo);

            if (movieResults == null || movieResults.Length == 0)
            {
                InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("Couldn't find title {0}", mediaFileInfo.TitleName)));
                return;
            }

            InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("Maching titles result for {0}", mediaFileInfo.TitleName)));
            TitleResult machedMovied = FindMachedMovie(mediaFileInfo, movieResults);
            if (machedMovied == null)
            {
                InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("No title mach founded for {0}", mediaFileInfo.TitleName)));
                return;
            }

            FindSubtitles(mediaFileInfo, machedMovied);
        }

        public virtual void DownloadSubtitle(string link, TitleFileInfo mediaFolderInfo)
        {
            string tempPath = Path.GetTempPath();
            string tempFileName = Path.Combine(tempPath, "Subtitles.zip");
            if (File.Exists(tempFileName))
                File.Delete(tempFileName);

            string extractedSubsPath = Path.Combine(mediaFolderInfo.FolderPath, "Temp");
            var webClient = new WebClient();
            webClient.DownloadFile(link, tempFileName);
            var unzipper = new FastZip();
            unzipper.ExtractZip(tempFileName, extractedSubsPath, FastZip.Overwrite.Always, null, ".srt", "", false);

            foreach (FileInfo file in new DirectoryInfo(extractedSubsPath).GetFiles("*.srt"))
            {
                File.Move(file.FullName, Path.Combine(mediaFolderInfo.FolderPath, string.Format("{0}.srt", mediaFolderInfo.FileNameNoExt)));
            }
            InvokeProgressChanged(new SubsProgressEventArgs(mediaFolderInfo, 0, string.Format("Subtitles successfully downloaded for {0}", mediaFolderInfo.TitleName)));

            if (File.Exists(tempFileName))
                File.Delete(tempFileName);

            if (Directory.Exists(extractedSubsPath))
                Directory.Delete(extractedSubsPath);
        }

        protected abstract TitleResult[] GetTitles(TitleFileInfo mediaFileInfo);

        protected abstract SubtitleResult[] GetSubtitles(TitleFileInfo mediaFileInfo, TitleResult movieResult);

        #region Private Methods

        /// <summary>
        /// Find the title that mached the given media file info
        /// </summary>
        /// <param name="mediaFileInfo">media file info to search</param>
        /// <param name="movieResults">movie result to search for subs</param>
        /// <returns>mached movie result</returns>
        private TitleResult FindMachedMovie(TitleFileInfo mediaFileInfo, IEnumerable<TitleResult> movieResults)
        {
            TitleResult machedMovied = null;

            foreach (TitleResult result in movieResults)
            {
                if (mediaFileInfo.IsSeries != result.IsSeries ||
                    (result.Name.ToLower() != mediaFileInfo.TitleName.ToLower()
                    /*&& result.Name.ToLower() != mediaFileInfo.TitleFileInfoByFolderName.TitleName.ToLower()*/)) continue;

                if (CompareYear && mediaFileInfo.Year.Length > 0 && result.Year == mediaFileInfo.Year)
                {
                    machedMovied = result;
                    InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("Matched title founded for {0}", mediaFileInfo.TitleName)));
                    break; // TODO: might not be correct. Was : Exit For
                }
                if (!CompareYear)
                {
                    machedMovied = result;
                    InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("Matched title founded for {0}", mediaFileInfo.TitleName)));
                    break; // TODO: might not be correct. Was : Exit For
                }
            }

            if (machedMovied == null)
            {
                InvokeProgressChanged(new SubsProgressEventArgs(mediaFileInfo, 0, string.Format("Couldn't mach title results for {0} please select.", mediaFileInfo.TitleName)));

                var noMatchFoundEventArgs = new NoMatchTitleFoundEventArgs(mediaFileInfo, (TitleResult[])movieResults);
                InvokeNoMovieMachFound(noMatchFoundEventArgs);
                if (noMatchFoundEventArgs.UserSelection != null)
                {
                    machedMovied = noMatchFoundEventArgs.UserSelection;
                }
            }
            return machedMovied;
        }

        /// <summary>
        /// Find the subtitle that mached to the title
        /// </summary>
        /// <param name="titleFileInfo">title to search for subtitles</param>
        /// <param name="machedMovied">movie to search for subtitles</param>
        /// <returns>subtitle result if founded or user select</returns>
        private SubtitleResult FindSubtitleResult(TitleFileInfo titleFileInfo, TitleResult machedMovied)
        {
            SubtitleResult subtitleResult = null;
            SubtitleResult[] subtitleResults = GetSubtitles(titleFileInfo, machedMovied);

            if (subtitleResults == null || subtitleResults.Length == 0)
            {
                InvokeProgressChanged(new SubsProgressEventArgs(titleFileInfo, 0, string.Format("Couldn't find subtitles for {0}", titleFileInfo.TitleName)));
                return subtitleResult;
            }

            foreach (var result in subtitleResults)
            {
                titleFileInfo.ToString();
                string subsName = result.Name.ToLower().Replace('.', ' ').Trim();
                string titleName = titleFileInfo.FileNameNoExt.ToLower().Replace('.', ' ').Trim();
                if (subsName == titleName)
                {
                    subtitleResult = result;
                    InvokeProgressChanged(new SubsProgressEventArgs(titleFileInfo, 0, string.Format("Matched subtitle founded for {0}", titleFileInfo.TitleName)));
                    break;
                }
            }

            if (subtitleResult == null)
            {
                //calculate subs priority
                foreach (var result in subtitleResults)
                {
                    if (titleFileInfo.IsHD && titleFileInfo.FullHD.Value && result.Name.ToLower().Contains("1080p"))
                        result.Priority++;
                    else if (titleFileInfo.IsHD && !titleFileInfo.FullHD.Value && result.Name.ToLower().Contains("720p"))
                        result.Priority++;
                    else if (!titleFileInfo.IsHD && !result.Name.ToLower().Contains("1080p") && !result.Name.ToLower().Contains("720p"))
                        result.Priority++;
                    if (SameGroup(titleFileInfo.FileNameNoExt, result.Name, '.'))
                        result.Priority++;
                    if (SameGroup(titleFileInfo.FileNameNoExt, result.Name, ' '))
                        result.Priority++;
                    if (SameGroup(titleFileInfo.FileNameNoExt, result.Name, '-'))
                        result.Priority++;
                }
                List<SubtitleResult> tempSubtitleResults = new List<SubtitleResult>(subtitleResults);
                tempSubtitleResults.Sort();
                subtitleResults = tempSubtitleResults.ToArray();

                //if there is subs result and is pririt higher then 0 and there is only one if there is only one result = true
                //if there is more then one result and the first subs prioirty is higher then the second = true
                if (SmartSearch && subtitleResults.Length > 0 && subtitleResults[0].Priority > 0
                    && (subtitleResults.Length == 1 || (subtitleResults.Length > 2 && subtitleResults[0].Priority > subtitleResults[1].Priority)))
                {
                    InvokeProgressChanged(new SubsProgressEventArgs(titleFileInfo, 0, string.Format("Matched subtitle founded for {0} using priority: {1}", titleFileInfo.TitleName, subtitleResults[0].Name)));
                    subtitleResult = subtitleResults[0];

                }
                else
                {
                    InvokeProgressChanged(new SubsProgressEventArgs(titleFileInfo, 0, string.Format("Couldn't mach subtitle results for {0} please select.", titleFileInfo.TitleName)));
                    var noMatchSubtitleFoundEventArgs = new NoMatchSubtitleFoundEventArgs(titleFileInfo, subtitleResults);
                    InvokeNoSubtitleMachFound(noMatchSubtitleFoundEventArgs);
                    if (noMatchSubtitleFoundEventArgs.UserSelection != null)
                    {
                        subtitleResult = noMatchSubtitleFoundEventArgs.UserSelection;
                    }
                }
            }
            return subtitleResult;
        }

        private bool SameGroup(string fileNameNoExt, string subsName, char seperator)
        {
            int index = fileNameNoExt.LastIndexOf(seperator);
            if (index < 0)
                return false;

            string titleGroupName = fileNameNoExt.Substring(index);
            index = subsName.LastIndexOf(seperator);
            if (index < 0)
                return false;

            string subsGroupName = subsName.Substring(index);
            return titleGroupName.ToLower() == subsGroupName.ToLower();
        }

        #endregion

        #region Event Handlers

        protected virtual void OnProgressChanged(SubsProgressEventArgs e)
        {

        }

        protected virtual void OnNoSubtitleMachFound(object sender, NoMatchSubtitleFoundEventArgs e)
        {

        }

        protected virtual void OnNoTitleMachFound(object sender, NoMatchTitleFoundEventArgs e)
        {

        }

        protected void InvokeFileFounded(SubsProgressEventArgs e)
        {
            Action<SubsProgressEventArgs> handler = FileFounded;
            if (handler != null) handler(e);
        }

        protected void InvokeProgressChanged(SubsProgressEventArgs e)
        {
            Action<SubsProgressEventArgs> handler = ProgressChanged;
            if (handler != null) handler(e);
        }

        protected void InvokeSearchedFileChanged(SubsProgressEventArgs e)
        {
            Action<SubsProgressEventArgs> handler = SearchedFileChanged;
            if (handler != null) handler(e);
        }

        protected void InvokeNoMovieMachFound(NoMatchTitleFoundEventArgs e)
        {
            Action<object, NoMatchTitleFoundEventArgs> handler = NoTitleMachFound;
            if (handler != null) handler(this, e);
        }

        protected void InvokeNoSubtitleMachFound(NoMatchSubtitleFoundEventArgs e)
        {
            Action<object, NoMatchSubtitleFoundEventArgs> handler = NoSubtitleMachFound;
            if (handler != null) handler(this, e);
        }

        #endregion
    }
}
